#include "TcpClient.h"
#include "../base/logging.h"
#include "Connector.h"
#include "EventLoop.h"
#include "SocketsOps.h"
#include <stdio.h>

using namespace placeholders;

namespace detail
{

	void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn)
	{
		//queueInloop or runInLoop?
		loop->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
	}

	void removeConnector(const ConnectorPtr& connector)
	{
		//connector->
	}

}

TcpClient::TcpClient(EventLoop* loop,
	const InetAddress& serverAddr,
	const string& nameArg)
	: m_pLoop(CHECK_NOTNULL(loop)),
	  m_ptrConnector(new Connector(loop, serverAddr)),
	  m_strName(nameArg),
	  m_connCallback(defaultConnectionCallback),
	  m_msgCallback(defaultMessageCallback),
	  m_bRetry(false),
	  m_bConnect(false),
	  m_nextConnId(1)
{
	m_ptrConnector->setNewConnectionCallback(std::bind(&TcpClient::newConnceton, this, _1));
	// FIXME setConnectFailedCallback
	LOG_INFO << "TcpClient::TcpClient[" << m_strName
		<< "] - connector " << m_ptrConnector.get();
}

TcpClient::~TcpClient()
{
	LOG_INFO << "TcpClient::~TcpClient[" << m_strName
		<< "] - connector " << m_ptrConnector.get();
	TcpConnectionPtr conn;
	bool unique = false;
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		unique = m_ptrConnection.unique();
		conn = m_ptrConnection;
	}
	if (conn)
	{
		assert(m_pLoop == conn->getLoop());
		// FIXME: not 100% safe, if we are in different thread
		CloseCallback cb = std::bind(&detail::removeConnection, m_pLoop, std::placeholders::_1);
		m_pLoop->runInLoop(
			std::bind(&TcpConnection::setCloseCallback, conn, cb));
		if (unique)
		{
			conn->forceClose();
		}
	}
	else
	{
		m_ptrConnector->stop();
		// FIXME: HACK
		// loop_->runAfter(1, boost::bind(&detail::removeConnector, connector_));
	}
}

void TcpClient::connect()
{
	LOG_INFO << "TcpClient::connect[" << m_strName << "] - connecting to "
		<< m_ptrConnector->serverAddress().toHostPort();
	m_ptrConnector->start();
	m_bConnect = true;
}

void TcpClient::disconnect()
{
	m_bConnect = false;
	std::lock_guard<std::mutex> lock(m_mutex);
	if (m_ptrConnection)
	{
		m_ptrConnection->shutdown();
	}
}

void TcpClient::stop()
{
	m_bConnect = false;
	m_ptrConnector->stop();
}

bool TcpClient::retry() const
{
	return m_bRetry;		//return ?
}

void TcpClient::newConnceton(int sockfd)
{
	m_pLoop->assertInLoopThread();
	InetAddress peerAddr(sockets::getPeerAddr(sockfd));
	char buf[32];
	snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toHostPort().c_str(), m_nextConnId);
	++m_nextConnId;
	string connName = m_strName + buf;

	InetAddress localAddr(sockets::getLocalAddr(sockfd));
	TcpConnectionPtr conn = std::make_shared<TcpConnection>(
		m_pLoop, connName, sockfd, localAddr, peerAddr);
	conn->setConnCallback(m_connCallback);
	conn->setMsgCallback(m_msgCallback);
	conn->setWriteCompleteCallback(m_writeCompeleteCallback);
	conn->setCloseCallback(
		std::bind(&TcpClient::removeConnection, this, _1));
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_ptrConnection = conn;
	}
	conn->connectEstablished();
}

///not thread safe,but in loop
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
	m_pLoop->assertInLoopThread();
	assert(m_pLoop == conn->getLoop());
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		assert(m_ptrConnection == conn);
		m_ptrConnection.reset();
	}
	m_pLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
	if (m_bRetry && m_bConnect)
	{
		LOG_INFO << "TcpClient::connect[" << m_strName << "] - Rconnecting to "
			<< m_ptrConnector->serverAddress().toHostPort();
		m_ptrConnector->restart();
	}
}